package com.marvin.springBoot.repository.support;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.EntityManager;
import javax.persistence.criteria.*;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.*;

/**
 * @author marvin.ma
 * @create 2017-09-28 8:40
 * @desc 条件查询扩展类
 **/
public class Query {

    private static Logger logger = LoggerFactory.getLogger(Query.class);

    private EntityManager entityManager;

    /** 要查询的模型对象 */
    private Class clazz;

    /** 查询条件列表 */
    private Root from;

    private List<Predicate> predicates;

    private CriteriaBuilder criteriaBuilder;

    private CriteriaQuery criteriaQuery;

    /** 排序方式列表 */
    private List<Order> orders;

    /** 子查询 */
    private Map<String, Query> subQuery;

    /** 关联查询 */
    private Map<String, Query> linkQuery;

    private String projection;

    /** 或条件 */
    private List<Query> orQuery;

    private String groupBy;

    public Query() { }

    /** 增加子查询 */
    private void addSubQuery(String propertyName, Query query) {
        if (this.subQuery == null) {
            this.subQuery = new HashMap<>();
        }

        if (query.projection == null) {
            throw new RuntimeException("子查询字段未设置");
        }
        this.subQuery.put(propertyName, query);
    }

    private void addSubQuery(Query query) {
        addSubQuery(query.projection, query);
    }

    /** 增加关联查询 */
    public void addLinkQuery(String propertyName, Query query) {
        if (this.linkQuery == null) {
            this.linkQuery = new HashMap<>();
        }
        this.linkQuery.put(propertyName, query);
    }

    /**
     * NUll
     * @param propertyName
     */
    public void isNull(String propertyName) {
        this.predicates.add(criteriaBuilder.isNull(from.get(propertyName)));
    }

    /**
     * 非 NULL
     * @param propertyName
     */
    public void isNotNull(String propertyName) {
        this.predicates.add(criteriaBuilder.isNotNull(from.get(propertyName)));
    }

    /**
     * 相等
     */
    public void eq(String propertyName, Object value) {
        if(isEmpty(value))
            return;
        this.predicates.add(criteriaBuilder.equal(from.get(propertyName), value));
    }

    /**
     * 不相等
     * @param propertyName
     * @param value
     */
    public void notEq(String propertyName, Object value) {
        if (isEmpty(value)) {
            return;
        }
        this.predicates.add(criteriaBuilder.notEqual(from.get(propertyName), value));
    }

    /**
     * 或
     */
    public void or(List<String> propertyName, Object value) {
        if (isEmpty(value)) {
            return;
        }
        if (null == propertyName || propertyName.size() == 0) {
            return;
        }
        Predicate predicate = criteriaBuilder.or(criteriaBuilder.equal(from.get(propertyName.get(0)), value));
        for (int i=1; i < propertyName.size(); ++i) {
            predicate = criteriaBuilder.or(predicate, criteriaBuilder.equal(from.get(propertyName.get(i)), value));
        }
        this.predicates.add(predicate);
    }

    /**
     * or like
     */
    public void orLike(List<String> propertyName, String value) {
        if (isEmpty(value) || (propertyName.size() == 0))
            return;
        if (value.indexOf("%") < 0)
            value = "%" + value + "%";
        Predicate predicate = criteriaBuilder.or(criteriaBuilder.like(from.get(propertyName.get(0)), value.toString()));
        for (int i = 1; i < propertyName.size(); ++i)
            predicate = criteriaBuilder.or(predicate, criteriaBuilder.like(from.get(propertyName.get(i)), value));
        this.predicates.add(predicate);
    }

    /**
     * in
     *
     * @param propertyName
     *            属性名称
     * @param value
     *            值集合
     */
    public void in(String propertyName, Collection value) {
        if ((value == null) || (value.size() == 0)) {
            return;
        }
        Iterator iterator = value.iterator();
        CriteriaBuilder.In in = criteriaBuilder.in(from.get(propertyName));
        while (iterator.hasNext()) {
            in.value(iterator.next());
        }
        this.predicates.add(in);
    }

    /**
     * not in
     *
     * @param propertyName
     *            属性名称
     * @param value
     *            值集合
     */
    public void notIn(String propertyName, Collection value) {
        if ((value == null) || (value.size() == 0)) {
            return;
        }
        Iterator iterator = value.iterator();
        CriteriaBuilder.In in = criteriaBuilder.in(from.get(propertyName));
        while (iterator.hasNext()) {
            in.value(iterator.next());
        }
        this.predicates.add(criteriaBuilder.not(in));
    }

    /**
     * 模糊匹配 like
     *
     * @param propertyName
     *            属性名称
     * @param value
     *            属性值
     */
    public void like(String propertyName, String value) {
        if (isEmpty(value))
            return;
        if (value.indexOf("%") < 0)
            value = "%" + value + "%";
        this.predicates.add(criteriaBuilder.like(from.get(propertyName), value));
    }

    /**
     * 时间区间查询
     *
     * @param propertyName
     *            属性名称
     * @param lo
     *            属性起始值
     * @param go
     *            属性结束值
     */
    public void between(String propertyName, Date lo, Date go) {
        if (isNotEmpty(lo) && isNotEmpty(go)) {
            this.predicates.add(criteriaBuilder.between(from.get(propertyName), lo, go));
            return;
        }

        // SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if (isNotEmpty(lo) && isEmpty(go)) {
            this.predicates.add(criteriaBuilder.lessThan(from.get(propertyName), lo));// formatter.format(lo)
            return;
        }

        if (isNotEmpty(go)) {
            this.predicates.add(criteriaBuilder.greaterThan(from.get(propertyName), go));
        }

    }

    /**
     * 数字区间查询
     * @param propertyName
     * @param lo
     * @param go
     */
    public void between(String propertyName, Number lo, Number go) {
        if (!(isEmpty(lo)))
            ge(propertyName, lo);

        if (!(isEmpty(go)))
            le(propertyName, go);
    }

    /**
     * 小于等于
     *
     * @param propertyName
     *            属性名称
     * @param value
     *            属性值
     */
    public void le(String propertyName, Number value) {
        if (isEmpty(value)) {
            return;
        }
        this.predicates.add(criteriaBuilder.le(from.get(propertyName), value));
    }

    /**
     * 小于
     *
     * @param propertyName
     *            属性名称
     * @param value
     *            属性值
     */
    public void lt(String propertyName, Number value) {
        if (isEmpty(value)) {
            return;
        }
        this.predicates.add(criteriaBuilder.lt(from.get(propertyName), value));
    }

    /**
     * 大于等于
     *
     * @param propertyName
     *            属性名称
     * @param value
     *            属性值
     */
    public void ge(String propertyName, Number value) {
        if (isEmpty(value)) {
            return;
        }
        this.predicates.add(criteriaBuilder.ge(from.get(propertyName), value));
    }

    /**
     * 大于
     *
     * @param propertyName
     *            属性名称
     * @param value
     *            属性值
     */
    public void gt(String propertyName, Number value) {
        if (isEmpty(value)) {
            return;
        }
        this.predicates.add(criteriaBuilder.gt(from.get(propertyName), value));
    }

    /**
     * 直接添加JPA内部的查询条件,用于应付一些复杂查询的情况,例如或
     * @param predicate
     */
    public void addCriterions(Predicate predicate) {
        this.predicates.add(predicate);
    }

    /**
     * 创建查询条件
     *
     * @return JPA离线查询
     */
    public CriteriaQuery newCriteriaQuery() {
        criteriaQuery.where(predicates.toArray(new Predicate[0]));
        if (!isEmpty(groupBy)) {
            criteriaQuery.groupBy(from.get(groupBy));
        }
        if (this.orders != null) {
            criteriaQuery.orderBy(orders);
        }
        addLinkCondition(this);
        return criteriaQuery;
    }

    /**
     * 创建关联查询
     * @param query
     */
    private void addLinkCondition(Query query) {

        Map subQuery = query.linkQuery;
        if (subQuery == null)
            return;

        for (Iterator queryIterator = subQuery.keySet().iterator(); queryIterator.hasNext();) {
            String key = (String) queryIterator.next();
            Query sub = (Query) subQuery.get(key);
            from.join(key);
            criteriaQuery.where(sub.predicates.toArray(new Predicate[0]));
            addLinkCondition(sub);
        }
    }

    /**
     * 组织排序sql
     * @param propertyName
     * @param order
     */
    public void addOrder(String propertyName, String order) {
        if (order == null || propertyName == null)
            return;

        if (this.orders == null)
            this.orders = new ArrayList();

        if (order.equalsIgnoreCase("asc"))
            this.orders.add(criteriaBuilder.asc(from.get(propertyName)));
        else if (order.equalsIgnoreCase("desc"))
            this.orders.add(criteriaBuilder.desc(from.get(propertyName)));
    }

    /**
     * 设置排序字段
     * @param propertyName
     * @param order
     */
    public void setOrder(String propertyName, String order) {
        this.orders = null;
        addOrder(propertyName, order);
    }

    public Class getClazz() {
        return this.clazz;
    }

    public void setFetchModes(List<String> fetchField, List<String> fetchMode) {

    }

    // ====================================================
    // ======================= 示例查询操作 =======================
    // ====================================================
    /**
     * 根据id查询
     *
     * @param entity
     * @return
     */
    public <T> T selectById(Class entity, Serializable id) {
        return (T)entityManager.find(entity, id);
    }

    /**
     * 判断一个对象是否为空。它支持如下对象类型：
     * <ul>
     * <li>null : 一定为空
     * <li>字符串 : ""为空,多个空格也为空
     * <li>数组
     * <li>集合
     * <li>Map
     * <li>其他对象 : 一定不为空
     * </ul>
     *
     * @param obj
     *            任意对象
     * @return 是否为空
     */
    public final static boolean isEmpty(final Object obj) {
        if (obj == null) {
            return true;
        }
        if (obj instanceof String) {
            return "".equals(String.valueOf(obj).trim());
        }
        if (obj.getClass().isArray()) {
            return Array.getLength(obj) == 0;
        }
        if (obj instanceof Collection<?>) {
            return ((Collection<?>) obj).isEmpty();
        }
        if (obj instanceof Map<?, ?>) {
            return ((Map<?, ?>) obj).isEmpty();
        }
        return false;
    }

    public final static boolean isNotEmpty(final Object obj) {
        return !isEmpty(obj);
    }

    public Root getFrom() {
        return from;
    }

    public void setFrom(Root from) {
        this.from = from;
    }

    public List<Predicate> getPredicates() {
        return predicates;
    }

    public void setPredicates(List<Predicate> predicates) {
        this.predicates = predicates;
    }

    public CriteriaBuilder getCriteriaBuilder() {
        return criteriaBuilder;
    }

    public void setCriteriaBuilder(CriteriaBuilder criteriaBuilder) {
        this.criteriaBuilder = criteriaBuilder;
    }

    public CriteriaQuery getCriteriaQuery() {
        return criteriaQuery;
    }

    public void setCriteriaQuery(CriteriaQuery criteriaQuery) {
        this.criteriaQuery = criteriaQuery;
    }

    public List<Order> getOrders() {
        return orders;
    }

    public void setOrders(List<Order> orders) {
        this.orders = orders;
    }

    public Map<String, Query> getSubQuery() {
        return subQuery;
    }

    public void setSubQuery(Map<String, Query> subQuery) {
        this.subQuery = subQuery;
    }

    public Map<String, Query> getLinkQuery() {
        return linkQuery;
    }

    public void setLinkQuery(Map<String, Query> linkQuery) {
        this.linkQuery = linkQuery;
    }

    public String getProjection() {
        return projection;
    }

    public void setProjection(String projection) {
        this.projection = projection;
    }

    public List<Query> getOrQuery() {
        return orQuery;
    }

    public void setOrQuery(List<Query> orQuery) {
        this.orQuery = orQuery;
    }

    public String getGroupBy() {
        return groupBy;
    }

    public void setGroupBy(String groupBy) {
        this.groupBy = groupBy;
    }

    public EntityManager getEntityManager() {
        return entityManager;
    }

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }
}
